package gsw.tool.arithmetic.encode;

import gsw.tool.common.Tool;
import gsw.tool.common.ToolMath;
import gsw.tool.common.ToolShow;

/**
 * 加密，是以某种特殊的算法改变原有的信息数据，使得未授权的用户即使获得了已加密的信息，但因不知解密的方法，仍然无法了解信息的内容。
 * 解密，是使用特定的密钥来还原加密过的信息。
 * 加密之所以安全，绝非因不知道加密解密算法方法，而是加密的密钥是绝对的隐藏，流行的RSA和AES加密算法都是完全公开的，一方取得已加密的数据，就算知道加密算法也好，若没有加密的密钥，也不能打开被加密保护的信息。
 * 在Android中，加密常用于信息传输和信息存储。
 * 常见的加密算法：SHA1、DES、Base64、RSA。
 */
public class SHA1 {

    // 初始结果集
    public static final int[] R = {0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0};

    public static void test() {
        String s = "abc";
        String result = SHA(s, R);
        ToolShow.log("加密前: " + s + " -- 加密后: " + result);
    }

    /**
     * SHA1
     * 安全哈希算法（Secure Hash Algorithm）主要适用于数字签名标准 （Digital Signature Standard DSS）里面定义的数字签名算法（Digital Signature Algorithm DSA），是一种不需要解密的算法。对于长度小于2^64位的消息，SHA1会产生一个160位的消息摘要。当接收到消息的时候，这个消息摘要可以用来验证数据的完整性。在传输的过程中，数据很可能会发生变化，那么这时候就会产生不同的消息摘要。
     * SHA1有如下特性：
     * 1. 不可以从消息摘要中复原信息；
     * 2. 两个不同的消息不会产生同样的消息摘要(有10 ^ 48分之一的机率出现相同的消息摘要,一般可以忽略)。
     * 3. SHA1算法中，消息相当于密钥，一个初始int数组RS相当于原消息。标准的SHA1算法使用统一的RS，自己实现SHA1算法时，可以修改RS的初始值。
     * <p>
     * SHA1同类算法：SHA224 、SHA256、 SHA384 、SHA512 、MD5 、HmacMD5、HmacSHA1 、HmacSHA224 、HmacSHA256 、HmacSHA384 、HmacSHA512 、PBKDF2。
     * <p>
     * 数字签名
     * <p>
     * 数字签名的原理是将要传送的明文通过一种函数运算（Hash）转换成报文摘要（不同的明文对应不同的报文摘要），报文摘要加密后与明文一起传送给接受方，接受方将接受的明文产生新的报文摘要与发送方的发来报文摘要解密比较，比较结果一致表示明文未被改动，如果不一致表示明文已被篡改。
     *
     * @param s 需要加密的数据
     * @param R 初始结果集,决定加密的最终结果
     */
    public static String SHA(String s, int[] R) {
        //1、转位字符串
        byte[] SD = convertBitString(s);
        //2、补位
        SD = coverPosition(SD);
        //3、添加长度
        SD = addLength(SD, s.getBytes().length);
        //4、初始化数据并进行加密
        int[] RS = initData(R, SD);
        //6、转换结果
        String result = convertHexString(RS);
        return result;
    }


    /**
     * 1、转位字符串
     * <p>
     * 将消息摘要S转换成位字符串，这里我们为了方便计算，将S转为byte数组SD，将SD对应的位字符串叫做SB；
     *
     * @param s 原消息
     * @return 转换的byte数组
     */
    private static byte[] convertBitString(String s) {
        if (Tool.isEmpty(s)) {
            return null;
        }
        byte[] data = s.getBytes();
        return data;
    }

    /**
     * 2、补位
     * <p>
     * 消息必须进行补位，即使消息之前已经满足条件，补位后SB的长度%512 = 448。补位规则是，在SB的后面一位补1，其余位补0。最少需要补1位，最多补512（即2的64次方）位。
     * 由于这里使用byte[]操作，所以添加的第一个byte为0x80，后面添加0x00。
     *
     * @param data 原消息S的byte数组SD
     * @return 补位后的SD
     */
    private static byte[] coverPosition(byte[] data) {
        if (data == null || data.length == 0) {
            return null;
        }
        // 补0x00的个数
        int fill = 0;
        // 原数组的长度
        int length = data.length;
        //相当于SB的长度%512
        int m = length % 64;
        //如果m小于56，就补1个0x80和55-m个0x00
        if (m < 56) {
            fill = 55 - m;
            //如果56<=m<64，就补1个0x80和(64 - m)+55个0x00。
        } else {
            fill = 55 + 64 - m;
        }

        //补位结果
        byte[] result = new byte[data.length + 1 + fill];
        //拷贝data
        System.arraycopy(data, 0, result, 0, length);
        // 补1个0x80
        result[length] = (byte) 0x80;
        //补fill个0x00,由于默认值就是0，所以不用操作
        return result;
    }

    /**
     * 3、添加长度
     * 用一个64位的位字符串表示原始SB的长度，把这个位字符串添加到补位后的SB后面。这样得到的SB的长度就是512的倍数。
     * 这里先把原始SD的长度转为long类型的原始SB的长度addLen，然后把addLen转为byte数组，添加到SD后面。
     *
     * @param data  补位后的SD
     * @param lengh 原SD的大小
     * @return 增加长度后的SD
     */
    private static byte[] addLength(byte[] data, int lengh) {
        if (data == null || data.length == 0) {
            return null;
        }
        //结果:增加8个byte，相当于增加64位的位字符串
        byte[] result = new byte[data.length + 8];
        //拷贝data
        System.arraycopy(data, 0, result, 0, data.length);
        //增加的长度
        long addLen = lengh * 8;
        //将增加的长度转byte数组
        byte[] lengthByte = ToolMath.longToBytes(addLen);
        System.arraycopy(lengthByte, 0, result, data.length, lengthByte.length);
        return result;
    }

    /**
     * 4、初始化数据
     * 用5个32的二进制数来保存加密的结果，这里我们使用长度为5的int数组RS来表示。我们还需要给RS设定初始值，统一使用下面的初始值：
     * int[] RS = {0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0};
     * 声明一个长度为80的int数组M来保存加密的中间结果，对M的前16位进行初始化：将SB分成n块，每块为512位的位字符串。将每个512位的位字符串平分成16份，放到M的前16位中，然后对M进行加密。加密操作见步骤5。
     *
     * @param R  初始结果集
     * @param SD 增加长度后的SD
     * @return 加密结果
     */
    private static int[] initData(int[] R, byte[] SD) {
        // 加密结果
        int[] RS = new int[5];
        //RS的初始值是R
        System.arraycopy(R, 0, RS, 0, R.length);
        // 加密的中间数据
        int[] M = new int[80];

        // 计算有几个512位的二进制数据
        int count = SD.length / 64;
        // 循环计算每一块的内容
        for (int i = 0; i < count; i++) {
            //将每个512位的位字符串平分成16份，放到M的前16位中
            for (int j = 0; j < 16; j++) {
                M[j] = byteArrayToInt(SD, i * 64 + j * 4);
            }
            //对RS加密
            encode(M, RS);
        }
        return RS;
    }

    /**
     * 5、加密
     * 加密分为以下几步：
     * 1. 补齐M：从M的第i(16<=i<=79)位开始，通过函数S(data,k)=(data << k) | (data >>> (32 - k))来补齐，这里k传入1，data传入M[i - 3] ^ M[i - 8] ^ M[i - 14] ^ M[i - 16];
     * 2. 声明函数与常量。声明4个函数：f1(x, y, z)= (x & y) | (~x & z); f2(x,  y, z) =x ^ y ^ z;f3(x, y,z)= (x & y) | (x & z) | (y & z);f4(x, y, z=x ^ y ^ z。声明4个常量：K1 = 0x5A827999 ；K2 = 0x6ED9EBA1 ；K3= 0x8F1BBCDC ；K4= 0xCA62C1D6。
     * 3. 修改RS:
     * 1. 声明：先声明一个临时的int数组TRS,最初TRS=RS，声明一个临时的int值temp。
     * 2. 遍历M:从M的第i(0<=i<=79)位开始，TRS[4] = TRS[3];TRS[3] = TRS[2];TRS[2] = S(TRS[1], 30);TRS[1] = TRS[0];TRS[0] = temp。
     * 3. 计算temp：当0<=i<=19时，temp = S(TRS[0], 5) + f1(TRS[1], TRS[2], TRS[3]) + TRS[4] + M[i] + K1;当20<=i<=39时，temp = S(TRS[0], 5) + f2(TRS[1], TRS[2], TRS[3]) + TRS[4] + M[i] + K2;当40<=i<=59时，temp = S(TRS[0], 5) + f3(TRS[1], TRS[2], TRS[3]) + TRS[4] + M[i] + K4;当60<=i<=79时，temp = S(TRS[0], 5) + f4(TRS[1], TRS[2], TRS[3]) + TRS[4] + M[i] + K4;
     * 4. 修改RS：从TRS的i位开始，RS[i]+=TRS[i]。然后清空M。
     *
     * @param m 加密的中间数据
     * @param h 加密结果
     */
    public static void encode(int[] m, int[] h) {
        //1.补齐M
        for (int i = 16; i <= 79; i++) {// 补齐m
            m[i] = S(m[i - 3] ^ m[i - 8] ^ m[i - 14] ^ m[i - 16], 1);
        }
        //2.声明函数与常量
        int K1 = 0x5A827999, K2 = 0x6ED9EBA1, K3 = 0x8F1BBCDC, K4 = 0xCA62C1D6;

        //3. 修改RS。
        //3.1 声明临时数组TRS，并初始化
        int[] TRS = new int[5];
        System.arraycopy(h, 0, TRS, 0, h.length);
        //3.2 遍历M并计算temp
        for (int i = 0; i < 4; i++) {
            for (int j = 0; j <= 19; j++) {
                int index = i * 20 + j;
                int temp = 0;
                if (i == 0) {
                    temp = S(TRS[0], 5) + f1(TRS[1], TRS[2], TRS[3]) + TRS[4] + m[index] + K1;
                } else if (i == 1) {
                    temp = S(TRS[0], 5) + f2(TRS[1], TRS[2], TRS[3]) + TRS[4] + m[index] + K2;
                } else if (i == 2) {
                    temp = S(TRS[0], 5) + f3(TRS[1], TRS[2], TRS[3]) + TRS[4] + m[index] + K3;
                } else if (i == 3) {
                    temp = S(TRS[0], 5) + f4(TRS[1], TRS[2], TRS[3]) + TRS[4] + m[index] + K4;
                }
                TRS[4] = TRS[3];
                TRS[3] = TRS[2];
                TRS[2] = S(TRS[1], 30);
                TRS[1] = TRS[0];
                TRS[0] = temp;
            }
        }

        //3.4修改RS，并清空M
        for (int i = 0; i < TRS.length; i++) {
            h[i] += TRS[i];
        }
        for (int i = 0; i < m.length; i++) {
            m[i] = 0;
        }
    }

    /**
     * 6、转换结果
     * 当SB的n块都加密完了，就得到了最终的结果RS。将RS转成16进制的字符串输出即可。
     *
     * @param RS 加密结果
     * @return 转换后16进制字符串结果
     */
    private static String convertHexString(int[] RS) {
        if (Tool.isEmpty(RS)) {
            return null;
        }
        StringBuffer sb = new StringBuffer();
        for (int in : RS) {
            byte[] bytes = ToolMath.intToBytes(in);
            sb.append(ToolMath.bytesToHexString(bytes));
        }
        return sb.toString();
    }


    /**
     * 字节数组转整数
     *
     * @param bytes 需要转换的字节数组
     * @param i     转换开始的位置
     * @return 转换的整数
     */
    public static int byteArrayToInt(byte[] bytes, int i) {
        return ((bytes[i] & 0xFF) << 24) | ((bytes[i + 1] & 0xFF) << 16) | ((bytes[i + 2] & 0xFF) << 8)
                | ((bytes[i + 3] & 0xFF) << 0);
    }

    /**
     * 对一个整数进行循环移位
     *
     * @param data 需要移位的数据
     * @param i    移位的个数,0<=i<=32
     * @return
     */
    public static int S(int data, int i) {
        return (data << i) | (data >>> (32 - i));
    }

    public static int f1(int x, int y, int z) {
        return (x & y) | (~x & z);
    }

    public static int f2(int x, int y, int z) {
        return x ^ y ^ z;
    }

    public static int f3(int x, int y, int z) {
        return (x & y) | (x & z) | (y & z);
    }

    public static int f4(int x, int y, int z) {
        return x ^ y ^ z;
    }

}
